﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.IO;

namespace EntropiaMiningMap
{
    public partial class frmMiningMap : Form
    {
        /// <summary>
        /// Single map containing all data for materials, etc.
        /// </summary>
        private Map map;

        /// <summary>
        /// Graphics object used in drawing map
        /// </summary>
        private System.Drawing.Graphics g;

        /// <summary>
        /// Pen used in drawing the map
        /// </summary>
        private Pen p;

        /// <summary>
        /// Initialize the form
        /// </summary>
        public frmMiningMap()
        {
            InitializeComponent();

            // Load and initialize the map
            LoadAndInitializeMap();
        }

        /// <summary>
        /// Draws the map and all coordinates associated with the map of materials
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DrawMap(object sender, PaintEventArgs e)
        {
            // Clear the graphics object before doing anything else
            g.Clear(Color.White);

            // Map width and height scaled to physical map, makes for fewer calculations
            //map.Center.X + (map.Width * (((float)(e.X) / pnMap.Width) - 0.5f)) / map.Zoom;

            float mapWidth = map.Width / map.Zoom;//(float) pnMap.Width * map.Zoom / (float) map.Width;
            float mapHeight = (float) pnMap.Height * map.Zoom / (float) map.Width;

            // Go through each material in the draw list
            foreach (Material m in map.DrawList.Values)
            {
                // Draw any points related to the material
                foreach (MaterialPoint mp in m.MaterialPoints)
                {
                    float x = map.Center.X + mapWidth * (float)mp.X * mapWidth;
                    float y = (float)mp.Y * mapHeight;
                    g.DrawEllipse(p, (float) mp.X * mapWidth, (float) (map.Height - mp.Y) * mapHeight, 1.0f, 1.0f);
                }
            }
        }

        /// <summary>
        /// Add a material to the list of all materials
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AddMaterial(object sender, EventArgs e)
        {
            String matName = txtMaterialName.Text.ToLower();

            // Attempt to find the material name
            if (!(map.Materials.ContainsKey(matName)))
            {
                // If the material does not exist, we must add it to the list of materials
                map.AddNewMaterial(matName, Int32.Parse(txtX.Text), Int32.Parse(txtY.Text));

                // Add the material name to the combo check-list
                clbMaterials.Items.Add(matName);
            }
            else
            {
                // The material exists, so we must add a new point to the material
                map.Materials[matName].AddPoint(Int32.Parse(txtX.Text), Int32.Parse(txtY.Text));

                // If the item is checked, update the map
                if (clbMaterials.CheckedItems.Contains(matName))
                {
                    DrawMap(this, new PaintEventArgs(g, new Rectangle(0, 0, pnMap.Width, pnMap.Height)));
                }
            }
        }

        /// <summary>
        /// Load and initialize the map associated with the form
        /// </summary>
        private void LoadAndInitializeMap()
        {
            // Initialize the graphics objects
            g = pnMap.CreateGraphics();
            p = new Pen(System.Drawing.Color.Black, 2.0f);
            
            // Initialize the map
            map = new Map();

            // Load the map data
            LoadData();

            // Set the background color to white
            pnMap.BackColor = Color.White;
            g.Clear(Color.White);

            // Add an event handler for the map
            pnMap.MouseWheel += new System.Windows.Forms.MouseEventHandler(ZoomMap);
            pnMap.MouseClick += new System.Windows.Forms.MouseEventHandler(SetFocus);
            this.MouseClick += new System.Windows.Forms.MouseEventHandler(LoseFocus);
        }

        /// <summary>
        /// Move the mouse and update the coordinates
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateCoordinates(object sender, MouseEventArgs e)
        {
            // TODO:
            // Fix coordinate calculation when zoomed in

            // Find the percentile values for position (this permits us to calculate whether we are
            // on the map as well as giving exact coordinates if we are on the map)
            float x = map.Center.X + (map.Width * (((float)(e.X) / pnMap.Width) - 0.5f)) / map.Zoom;// map.HalfWidth / map.Zoom * (((float)(e.X) / pnMap.Width) - 0.5f);

            float a = map.Width * (((float)(e.X) / pnMap.Width) - 0.5f) / map.Zoom;
            float b = map.Width / map.Zoom * (((float)(e.X) / pnMap.Width) - 0.5f);

            float j = 0;
            //x = i;
            
            float y = (float) (map.Height - e.Y) * map.Zoom / pnMap.Height;

            // If we are able to update the coordinates according to boundaries update the mouse coordinates
            //if (x >= 0.0f && x <= 1.0f && y >= 0.0f && y <= 1.0f)
            //{
                lblCoordinates.Text = "(" + (int) x + ", " + (int) (y * map.Height) + ")";
            //}
        }

        /// <summary>
        /// Update the draw list to make sure only correct materials are being displayed on the map
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateDrawList(object sender, ItemCheckEventArgs e)
        {
            // If the value will become checked
            if (e.NewValue == CheckState.Checked)
            {
                // Add it to the draw list
                map.AddToDrawList((String) clbMaterials.Items[e.Index]);
            }
            else
            {
                // Otherwise, remove it from the draw list
                map.RemoveFromDrawList((String) clbMaterials.Items[e.Index]);
            }

            DrawMap(sender, new PaintEventArgs(g, new Rectangle()));
        }

        /// <summary>
        /// Save data when exiting program
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SaveData(object sender, FormClosingEventArgs e)
        {
            // Check for the directory, if it does not exist, create it
            if (!Directory.Exists("data"))
            {
                Directory.CreateDirectory("data");
            }

            // Create a new file within the data folder
            XmlTextWriter xtw = new XmlTextWriter("data\\materials.xml", null);

            // Formatting
            xtw.Formatting = Formatting.Indented;

            // Open the document
            xtw.WriteStartDocument();

            // Encompassing element
            xtw.WriteStartElement("MiningMap");

            // Iterate through each material
            foreach (Material m in map.Materials.Values)
            {
                // Write a material
                xtw.WriteStartElement("material");
                xtw.WriteStartAttribute("name");
                xtw.WriteString(m.Name);
                xtw.WriteEndAttribute();

                // Iterate through each point in the material
                foreach (MaterialPoint mp in m.MaterialPoints)
                {
                    // Write a material point
                    xtw.WriteStartElement("materialpoint");

                    // Write the x attribute
                    xtw.WriteStartAttribute("x");
                    xtw.WriteString(mp.X.ToString());
                    xtw.WriteEndAttribute();

                    // Write the y attribute
                    xtw.WriteStartAttribute("y");
                    xtw.WriteString(mp.Y.ToString());
                    xtw.WriteEndAttribute();

                    // Write the number of occurrences
                    xtw.WriteStartAttribute("occurrences");
                    xtw.WriteString(mp.Occurrences.ToString());
                    xtw.WriteEndAttribute();

                    // Close the material point element
                    xtw.WriteEndElement();
                }

                // Close the material element
                xtw.WriteEndElement();
            }

            // Close the encompassing element
            xtw.WriteEndElement();

            // Close the document
            xtw.WriteEndDocument();

            // Close the writer
            xtw.Close();
        }

        /// <summary>
        /// Load the map data from file
        /// </summary>
        private void LoadData()
        {
            // Check for the file, if it does not exist, do not continue with load
            if (!File.Exists("data\\materials.xml"))
            {
                return;
            }

            // Create a new file within the data folder
            XmlTextReader xtr = new XmlTextReader("data\\materials.xml");

            // Try to read the opening line
            xtr.Read();

            // While there is data to be read, continue reading the file
            while (!xtr.EOF)
            {
                // If it is a material
                if(xtr.LocalName.Equals("material"))
                {
                    // Create a material
                    xtr.MoveToNextAttribute();

                    Material m = new Material(xtr.Value);

                    // Create the various material points for this material
                    SkipWhiteSpace(ref xtr);
                    
                    // While we are looking at material points
                    while (xtr.LocalName.Equals("materialpoint"))
                    {
                        // x-coordinate
                        xtr.MoveToNextAttribute();
                        int x = Int32.Parse(xtr.Value);

                        // y-coordinate
                        xtr.MoveToNextAttribute();
                        int y = Int32.Parse(xtr.Value);

                        // number of occurrences
                        xtr.MoveToNextAttribute();
                        int occ = Int32.Parse(xtr.Value);

                        // Create the material point
                        MaterialPoint mp = new MaterialPoint(x, y, occ);
                        m.LoadPoint(mp);

                        // Read the next node
                        SkipWhiteSpace(ref xtr);
                    }

                    // Add the material to the list and map
                    map.LoadMaterial(m);
                    clbMaterials.Items.Add(m.Name);
                }

                // Only read if we are not at the end of an element
                SkipWhiteSpace(ref xtr);
            }

            // We have finished reading, so close the document
            xtr.Close();
        }

        /// <summary>
        /// Helper method to skip white spaces
        /// </summary>
        /// <param name="xtr"></param>
        private void SkipWhiteSpace(ref XmlTextReader xtr)
        {
            // Read the next line
            xtr.Read();

            // Skip white space
            while (xtr.Value.Contains("\r\n"))
            {
                xtr.Read();
            }
        }

        /// <summary>
        /// Controls zoom for the map
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ZoomMap(object sender, MouseEventArgs e)
        {
            // If delta is positive, we are zooming in, otherwise we are zooming out
            if (e.Delta > 0)
            {
                map.ZoomIn();
            }
            else if (e.Delta < 0)
            {
                map.ZoomOut();
            }

            // Redraw the map
            DrawMap(sender, new PaintEventArgs(g, new Rectangle()));
        }

        /// <summary>
        /// Sets the focus to the map
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SetFocus(object sender, MouseEventArgs e)
        {
            pnMap.Focus();
        }

        /// <summary>
        /// Set the focus to the form (i.e. click off map)
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void LoseFocus(object sender, MouseEventArgs e)
        {
            lblCoordinates.Focus();
        }
    }
}
